﻿/**
 \file parser.c
 Исходный синтаксического и семантического анализаторов.
*/
#include "common.h"
#include "scanner.h"
#include "table.h"
#include <string.h>

/* Номера базовых типов в basetypes */
#define BOOLEAN  0
#define CHAR     1
#define BYTE     2
#define WORD     3
#define DWORD    4
#define SHORT    5
#define INTEGER  6
#define LONGINT  7
#define REAL     8
#define LONGREAL 9
#define STRING   10
#define NIL      11
#define COMMON   12
#define CLASS    13

/**
 \var struct Item *basetypes[12];
 Массив указателей на базовые типы данных.
 Чтобы не запускать каждый раз процедуру поиска в таблице,
 лучше сразу поместить указатели на базовые типы в массив
 и потом обращаться к нему, что увеличивает производительность.
*/
struct Item *basetypes[14];

/** 
 \var unsigned long int NumType;
 Номер промежуточного типа.
*/
unsigned long int NumType = 0;

/**
 \fn void AddType(const char *nametype, char type, char size)
 Добавляет один из базовых типов данных в основную таблицу символов.
 \param nametype Имя типа.
 \param type Номер позиции в массиве basetypes.
 \param size Размер типа.
*/
void AddType(const char *nametype, char type, char size)
{
 union Data dat;
 struct Item *item = NULL;
 
 dat.parent = NULL;
 item = AddItem(GetCurLevel()->items,(char *)nametype);
 FillItem(item,cType,tBase,GLOBAL,CLOSE,size,dat,NULL);
 basetypes[type] = item;
}

/** 
 \fn void DefineBaseTypesAndFunc(void)
 Добавляет в основную таблицу символов предопределенные типы и процедуры.
*/
void DefineBaseTypesAndFunc(void)
{
 struct Item *item = NULL;
 union Data one;

 AddType("boolean",BOOLEAN,1);
 SetPredScope(GetCurLevel()->items);
 AddType("char",CHAR,1);
 AddType("byte",BYTE,1);
 AddType("word",WORD,2);
 AddType("dword",DWORD,4);
 AddType("short",BYTE,1);
 AddType("integer",WORD,2);
 AddType("longint",DWORD,4);
 AddType("real",REAL,4);
 AddType("longreal",LONGREAL,8);
 AddType("@string",STRING,4);
 AddType("@nil",NIL,4);

 item = AddItem(GetCurLevel()->items,"@common");
 one.parent = NULL;
 FillItem(item,cType,tBase,GLOBAL,CLOSE,0,one,NULL);
 basetypes[COMMON] = item;
 one.parent = item;
 item = AddItem(GetCurLevel()->items,"@class");
 FillItem(item,cType,tBase,GLOBAL,CLOSE,4,one,NULL);
 basetypes[CLASS] = item;
}

/** 
 \fn void error(char *text)
 Выводит ошибку и завершает работу.
 \param text Текст ошибки.
*/
void error(char *text)
{
 printf(ETEMPLATE,GetCurLine(),text);
 exit(1);
}

/** 
 \fn unsigned char test(unsigned char testlex, char *text)
 Является ли текущая лексема ожидаемой?
 \param testlex Ожидаемая лексема.
 \param text Текст ошибки.
 \return Ксли найдена ожидаемая лексема, то возращается ее код.
*/
unsigned char test(unsigned char testlex, char *text)
{
 unsigned char lex = 0;

 GetToken();
 lex = GetCurToken();
 if (lex != testlex)
  error(text);
 return lex;
}

/**
 \fn char *MakeTypeName(void)
 Создает имя для промежуточного типа.
 \return Возвращает указатель на строку с созданным именем.
*/
char *MakeTypeName(void)
{
 char num[11];
 char *string = NULL;

 string = (char *)GetMem(14,"MakeTypeName");
 string[0] = 0;
 ++NumType;
 itoa(NumType,(char *)num,10);
 strcat(string,"#IT");
 strcat(string,(const char *)num);
 return string;
}

/**
 \fn struct Item *IsDeclaredType(void)
 Проверяет указанный тип на существование в таблице символов.
 \return Если текущий идентификатор уже определен в таблице символов - программа
         завершится с ошибкой, в противном случае будет возвращен указатель на этот
         элемент.
*/
struct Item *IsDeclaredType(void)
{
 struct Item *item = NULL;

 item = Search((char *)GetIdentValue(),cType);
 if ((GetCurToken() == lIdent) && (item == NULL))
  error(eNotFindType);
 return item;
}

/** 
 \struct Names
 Структура, содержащая имена переменных и сведения об их открытости.
*/
struct Names {
 unsigned char *name; /** Имя переменной. */
 unsigned char open;  /** Открытость переменной. */
 struct Names *next;  /** Указатель на следующий элемент Names */
};

/**
 \fn void AddVariable(struct Names *ListOfNames, struct Item *parent,
                      unsigned long int *size, unsigned long int *offset,
                      struct Item *TypeOfVar, unsigned char scope)
 Добавляет считанные переменные в таблицу символов.
 \param ListOfNames Список имен переменных.
 \param parent Указатель на список, в который добавляются переменные.
 \param size Указатель на счетчик общего объема памяти занимаемого переменными.
 \param offset Указатель на счетчик смещения.
 \param TypeOfVar Указатель на тип переменной.
 \param scope Область видимости переменных.
*/
void AddVariable(struct Names *ListOfNames, struct Item *parent,
                 unsigned long int *size, unsigned long int *offset,
                 struct Item *TypeOfVar, unsigned char scope)
{
 struct Names *temp = NULL;
 struct Item *itm = NULL;
 union Data dat;

 temp = ListOfNames;
 dat.parent = NULL;
 while (temp != NULL) {
  itm = AddItem(parent,(char *)temp->name);
  FillItem(itm,cVar,tSimple,scope,
           temp->open,TypeOfVar->size,dat,TypeOfVar);
  *size = *size + TypeOfVar->size;
  *offset = *offset + TypeOfVar->size;
  temp = temp->next;
 }
}

unsigned long int DeclarationVar(struct Item *parent, unsigned char scope);
/** 
 \fn struct Item *Record(const char *name, unsigned char scope, 
                         unsigned char flags)
 Добавляет в таблицу символов структуру.
 \param name Имя структуры.
 \param scope Область видимости.
 \param flags Открытость структуры.
 \return Возвращает указатель на созданную структуру.
*/
struct Item *Record(const char *name, unsigned char scope, 
                    unsigned char flags)
{
 struct Item *it = NULL, *itm = NULL,
             *item = NULL, *temp = NULL;
 union Data one;
 unsigned long int s = 0;

 one.parent = NULL;
 temp = AddItem(GetCurLevel()->items,(char *)name);
 FillItem(temp,cType,tRecord,scope,flags,0,one,NULL);
 item = (struct Item *)GetMem(sizeof(struct Item),"Record");
 FillItem(item,cType,tRecord,scope,flags,0,one,NULL);
 item->child = (struct Item *)GetMem(sizeof(struct Item),"Record");
 item->child->name = (unsigned char *)GetMem(2,"Record");
 item->child->name[0] = '.';
 item->child->name[1] = 0;
 item->child->next = item->child;
 one.parent = NULL;
 FillItem(item->child,cNone,0,0,0,0,one,NULL);
 GetToken();
 if (GetCurToken() == lLeftType1)
 {
  GetToken();
  if (GetCurToken() != lIdent)
   error(eNotFindInheritedType);
  if (!strcmp((const char *)GetIdentValue(),"class"))
   item->one.parent = basetypes[CLASS];
  else
  {
   it = IsDeclaredType();
   if (it->type != tRecord)
    error(eParentMustbeRecord);
   item->one.parent = it;
  }
  GetToken();
  if (GetCurToken() != lRightType1)
   error(eNotFindRightType1);
  GetToken();
 }
 else
  item->one.parent = basetypes[COMMON];
 s = DeclarationVar(item->child,LOCAL);
 if (it != NULL)
 {
  item->size = s + it->size;
  itm = item->child->next;
  while (strcmp((const char *)itm->name,"."))
  {
   itm->offset = itm->offset + it->size;
   itm = itm->next;
  }
 }
 else
  item->size = s;
 item->name = temp->name;
 item->next = temp->next;
 *temp = *item;
 if (GetCurToken() != lEnd)
  error(eNotFindEnd);
 test(lDotComma,eNotFindDotComma);
 return item; 
}

/**
 \fn struct Item *Pointer(const char *name, unsigned char scope,
                          unsigned char flags)
 Добавляет в таблицу новый указатель.
 \param name Имя типа.
 \param scope Область видимости.
 \param flags Открыт или закрыт.
 \return Возвращает указатель на созданный тип.
*/
struct Item *Pointer(const char *name, unsigned char scope,
                     unsigned char flags)
{
 struct Item *it = NULL, *item = NULL;
 unsigned char open = 0;
 unsigned long int len = 0;
 char *nametype = NULL; 
 union Data dat;

 dat.parent = NULL;
 test(lTo,eNotFindToKeyword);
 GetToken();
 switch (GetCurToken()) {
  case lIdent: {
   it = SearchItem(GetPredScope(),(char *)GetIdentValue());
   if (it != NULL)
    error(eTypeMustBeRecord);
   it = Search((char *)GetIdentValue(),cType);
   if (it != NULL)
   {
    if (it->type != tRecord)
     error(eTypeMustBeRecord);
    item = AddItem(GetCurLevel()->items,(char *)name);
    FillItem(item,cType,tPointer,scope,flags,4,dat,it);
    test(lDotComma,eNotFindDotComma);
    return item;
   }
   else
    error(eNotFindType);
  }
  case lRecord: {
   nametype = MakeTypeName();
   it = Record(nametype,scope,flags);
   item = AddItem(GetCurLevel()->items,(char *)name); 
   FillItem(item,cType,tPointer,scope,flags,4,dat,it);
   free(nametype);
   return item;
  }
 }
 return NULL;
}

struct Names *GetNames(void);
/**
 \fn struct Item *ProcedureHeader(const char *name, unsigned char cls,
                                  unsigned char scope, unsigned char flags)
 Добавляет в таблицу новый процедурный тип.
 \param name Имя типа.
 \param cls Класс: процедурный тип или процедура.
 \param scope Область видимости.
 \param flags Открытость типа.
 \return Возращает указатель на созданный тип.
*/
struct Item *ProcedureHeader(const char *name, unsigned char cls,
                             unsigned char scope, unsigned char flags)
{
 struct Item *item = NULL, *it = NULL, *tm = NULL;
 struct Names *LocalList = NULL, *temp = NULL;
 unsigned char classid = 0;
 unsigned long int size = 0;
 unsigned long int offset = 0;
 unsigned long int dim = 0;
 unsigned long int len = 0;
 union Data dat;

 if (GetCurToken() != lLeftType1) {
  if (GetCurToken() == lDoubleDot) {
   test(lIdent,eNotFindType);
   it = IsDeclaredType();
   if ((it->type == tRecord) || (it->type == tArray))
    error(eArrayOrRecordReturn);
   dat.parent = it;
   test(lDotComma,eNotFindDotComma);
   GetToken();
   item = AddItem(GetCurLevel()->items,(char *)name);
   FillItem(item,cls,tProc,scope,flags,4,dat,NULL);
   return item;
  }
  if (GetCurToken() != lDotComma)
   error(eNotFindDotComma);
  GetToken();
  dat.parent = NULL;
  item = AddItem(GetCurLevel()->items,(char *)name);
  FillItem(item,cls,tProc,scope,flags,4,dat,NULL);
  return item;
 }

 dat.parent = NULL;
 item = AddItem(GetCurLevel()->items,(char *)name);
 FillItem(item,cls,tProc,scope,flags,4,dat,NULL);
 item->child = (struct Item *)GetMem(sizeof(struct Item),"ProcedureHeader");
 FillItem(item->child,cNone,0,LOCAL,CLOSE,0,dat,NULL);
 item->child->name = (unsigned char *)GetMem(2,"ProcedureHeader");
 item->child->name[0] = '.';
 item->child->name[1] = 0;
 item->child->next = item->child;

 do {
  GetToken();
  switch (GetCurToken()) {
   case lConst:
    classid = CONST;
    break;
   case lVar:
    classid = VAR;
    break;
   case lIdent:
    classid = USUAL;
    break;
   default:
    error(eUnknownKeyword);
  }
  if ((classid == VAR) || (classid == CONST))
   GetToken();
  LocalList = GetNames();
  if (GetCurToken() != lDoubleDot)
   error(eNotFindDoubleDot);
  GetToken();
  switch (GetCurToken()) {
   case lIdent: {
    it = IsDeclaredType();
    if (classid == USUAL)
     if ((it->type == tRecord) || (it->type == tArray))
      error(eParamCantBeRecordOrArray);
    if (classid == VAR || classid == CONST)
     if (it->type == tProc)
      error(eProcTypeCantBeWithVarOrConst);
    AddVariable(LocalList,item->child,&size,&offset,it,classid);
    break;
   }
   case lArray: {
    if (classid == VAR || classid == CONST)
     error(eOpenArrayIsNotUsual);
    test(lOf,eNotFindOf);
    GetToken();
    it = IsDeclaredType();
    temp = LocalList;
    dat.parent = NULL;
    while (temp != NULL) {
     tm = AddItem(item->child,(char *)temp->name);
     FillItem(tm,cVar,tOpenArray,temp->open,
              classid,it->size,dat,it);
     size = size + it->size;
     offset = offset + it->size;
     temp = temp->next;
    }
    break;
   }
   default:
    error(eUnknownKeyword);
  }
  GetToken();
  while (LocalList != NULL) {
   temp = LocalList->next;
   if (LocalList->name != NULL)
    free(LocalList->name);
   free(LocalList);
   LocalList = temp;
  }
 } while (GetCurToken() == lDotComma);
 if (GetCurToken() != lRightType1)
  error(eNotFindRightType1);
 GetToken();
 if (GetCurToken() == lDoubleDot) {
  test(lIdent,eNotFindType);
  it = IsDeclaredType();
  if (it->type == tArray || it->type == tRecord)
   error(eArrayOrRecordReturn);
  item->one.parent = it;
  GetToken();
 }
 if (GetCurToken() != lDotComma)
  error(eNotFindDotComma);
 GetToken();
 return item;
} 

/**
 \fn void DeclarationType(unsigned char local)
 Считывает определения типов данных.
 \param local Область видимости.
*/
void DeclarationType(unsigned char local)
{
 char *str = NULL;
 unsigned char len = 0; 
 unsigned char open = 0;
 struct Item *item = NULL;

 GetToken();
 if (GetCurToken() != lIdent)
  error(eThereAreNotTypes);
 do {
  len = strlen((const char *)GetIdentValue());
  str = (char *)GetMem(len+1,"DeclarationType");
  strcpy(str,(const char *)GetIdentValue());
  str[len] = 0;
  GetToken();
  if (GetCurToken() == lMultiple) {
   GetToken();
   open = OPEN;
  }
  else
   open = CLOSE;
  if (GetCurToken() == lEqual)
   GetToken();
  else
   error(eNotFindEqual);
  
  switch (GetCurToken()) {
   /*case lArray: {
    Array(str,local,open);
    free(str);
    break;
   }*/
   case lRecord: {
    Record(str,local,open);
    free(str);
    GetToken();
    break;
   }
   case lProcedure: {
    GetToken();
    ProcedureHeader(str,cType,local,open);
    break;
   }
   case lPointer: {
    Pointer(str,local,open);
    free(str);
    GetToken();
    break;
   }
   default: {
    item = SearchItem(GetPredScope(),(char *)GetIdentValue());
    if (item != NULL)
     error(eSimpleTypeCantBeBasis);
    else
     error(eNotFindType);
    GetToken();
   }
  } 
 } while (GetCurToken() == lIdent);
}

/**
 \fn struct Names *GetNames(void)  
 Читает список имен.
 \return Возвращает указатель на список имен.
*/
struct Names *GetNames(void)
{
 struct Names *ListOfNames = NULL, 
              *temp        = NULL, 
              *current     = NULL;

 ListOfNames = (struct Names *)GetMem(sizeof(struct Names),"GetNames");
 ListOfNames->name = NULL;
 ListOfNames->next = NULL;
 ListOfNames->name = (unsigned char *)
        GetMem(strlen((const char*)GetIdentValue())+1,"GetNames");
 strcpy((char *)ListOfNames->name,(const char *)GetIdentValue());
 ListOfNames->name[strlen((const char *)GetIdentValue())] = 0;
 current = ListOfNames;
 GetToken();
 if (GetCurToken() == lMultiple) {
  ListOfNames->open = OPEN;
  GetToken();
 }
 else
  ListOfNames->open = CLOSE;
 while (GetCurToken() == lComma) {
  test(lIdent,eNoFindIdentAfterComma);
  temp = (struct Names *)GetMem(sizeof(struct Names),"GetNames");
  temp->name = NULL;
  temp->next = NULL;
  temp->name = (unsigned char *)
      GetMem(strlen((const char *)GetIdentValue())+1,"GetNames");
  strcpy((char *)temp->name,(const char *)GetIdentValue());
  temp->name[strlen((const char *)GetIdentValue())] = 0;
  GetToken();
  if (GetCurToken() == lMultiple) {
   temp->open = OPEN;
   GetToken();
  }
  current->next = temp;
  current = temp;
 }
 return ListOfNames;
}

/**
 \fn unsigned long int DeclarationVar(struct Item *parent, unsigned char scope)
 Считывает определения переменных и заносит их в таблицу символов.
 \param parent Указатель на список, в который добавляются переменные.
 \param scope Область видимости создаваемых переменных.
 \return Возвращает общий размер памяти занимаемый новыми переменными.
*/
unsigned long int DeclarationVar(struct Item *parent, unsigned char scope)
{
 struct Names *ListOfNames = NULL, *temp = NULL;
 struct Item *item = NULL, *it = NULL;
 unsigned long int size = 0;
 unsigned long int offset = 0;
 char *nametype = NULL;

 if (GetCurToken() != lIdent)
  error(eNotFindVars);
 do {
  ListOfNames = GetNames();
  if (GetCurToken() != lDoubleDot)
   error(eNotFindDoubleDot);
  GetToken();
  switch (GetCurToken()) {
   case lIdent: {
    it = IsDeclaredType();
    if (it->child == parent)
     error(eFieldCantRefsToYourStruct);
    test(lDotComma,eNotFindDotComma);
    AddVariable(ListOfNames,parent,&size,&offset,it,scope);
    GetToken();
    break;
   }
   case lRecord: {
    nametype = MakeTypeName();
    item = Record(nametype,LOCAL,CLOSE);
    AddVariable(ListOfNames,parent,&size,&offset,item,scope);
    free(nametype);
    GetToken();
    break;
   }
   case lPointer: {
    nametype = MakeTypeName(); 
    item = Pointer(nametype,LOCAL,CLOSE);
    AddVariable(ListOfNames,parent,&size,&offset,item,scope);
    free(nametype);
    GetToken();
    break;
   }
   case lArray: {
    nametype = MakeTypeName(); 
  //  item = Array(nametype,LOCAL,CLOSE);
    AddVariable(ListOfNames,parent,&size,&offset,item,scope);
    free(nametype);
    break;
   }
   case lProcedure: {
    nametype = MakeTypeName();
    GetToken();
    item = ProcedureHeader(nametype,cType,LOCAL,CLOSE);
    AddVariable(ListOfNames,parent,&size,&offset,item,scope);
    free(nametype);
    break;
   }
   default:
    error(eUnknownKeyword);
  }
  while (ListOfNames != NULL) {
   temp = ListOfNames->next;
   if (ListOfNames->name != NULL)
    free(ListOfNames->name);
   free(ListOfNames);
   ListOfNames = temp;
  }
 } while (GetCurToken() == lIdent);
 return size;
}

/**
 \fn void StartSyntaxAnalysis(void)
 Запускает цикл синтаксического анализа кода.
*/
void StartSyntaxAnalysis(void)
{
 struct Item *item = NULL;
 union Data dat;

 AddLevel(GENERAL_TABLE);
 DefineBaseTypesAndFunc();
 AddLevel(GENERAL_TABLE);
 test(lModule,eNotFindKeywordModule);
 test(lIdent,eNotFindNameOfModule);
 item = AddItem(GetCurLevel()->items,(char *)GetIdentValue());
 dat.parent = NULL;
 FillItem(item,cModule,0,GLOBAL,CLOSE,0,dat,NULL);
 test(lDotComma,eNotFindDotComma);
 GetToken();
 do {
  switch(GetCurToken())
  {
   case lVar: {
    GetToken();
    DeclarationVar(GetCurLevel()->items,GLOBAL);
    break;
   }
   case lType: {
    DeclarationType(GLOBAL);
    break;
   }
   default:
    if (GetCurToken() != lEnd)
     error(eUnknownKeyword);
  }
 } while (GetCurToken() != lEnd);
 if (GetCurToken() != lEnd)
  error(eNotFindEnd);
 test(lDot,eNotFindDot);
}